"""
添加到购物车:
    请求方式: POST /cart/
    请求参数: sku_id(商品编号int)  count(数量int)  selected(是否勾选,默认勾选bool)
    返回数据: sku_id(商品编号int)  count(数量int)  selected(是否勾选,默认勾选bool)

注: 访问此接口时,无论用户是否登录,前端请求都需哟啊携带请求头Authorization,即发送身份认证信息(JWT),由后端判断是否登录
因此后端需考虑用户是否登录JWT是否有值,重写perform_authentication()方法,此方法是drf检查用户身份的方法;在获取request.user属性时捕获异常,drf在返回user时,会检查Authorization请求头,无效的Authorization请求头会导致抛出一茶馆
"""
from django_redis import get_redis_connection
from rest_framework.response import Response
from rest_framework.views import APIView

from MeiduoMall.utils import myjson
from carts import constants
from carts.serializers import CartSerializer, CartSKUSerializer, CartDeleteSerializer, CartSelectAllSerializer
from goods.models import SKU


class CartView(APIView):
    """购物车"""

    def perform_authentication(self, request):
        """重写父类的用户验证方法,不在进入视图前就检查JWT"""
        pass

    def post(self, request):
        """
        添加到购物车
        请求方式: POST /cart/
        请求参数: sku_id(商品编号int)  count(数量int)  selected(是否勾选,默认勾选bool)
        返回数据: sku_id(商品编号int)  count(数量int)  selected(是否勾选,默认勾选bool)
        """
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        sku_id = serializer.validated_data.get('sku_id')
        count = serializer.validated_data.get('count')
        selected = serializer.validated_data.get('selected')

        # 判断用户是否登录
        try:
            user = request.user
        except Exception:
            user = None
        """
        print('serializer----', serializer)
        # data 字典Dict
        print('serializer.data----', serializer.data)
        # validated_data 有序字典OrderedDict
        print('serializer.validated_data----', serializer.validated_data)
        """
        # 构造响应对象
        response = Response(serializer.validated_data)

        if user is None:
            # 用户未登录,数据存入cookie  购物车物品{sku_id: count, sku_id:count,...} 选中的物品{sku_id, sku_id, ...}
            # 读取cookies中的数据
            cart_str = request.COOKIES.get('cart')
            if cart_str is None:
                cart_dict = {}
            else:
                cart_dict = myjson.loads(cart_str)
            # 取出cookie中原数量
            if sku_id in cart_dict:
                count_cart = cart_dict[sku_id]['count']
            else:
                count_cart = 0
            # 修改数据
            cart_dict[sku_id] = {
                'count': count + count_cart,  # 新添加的数量+原本cookie中数量
                'selected': True
            }
            # 保存到cookie中
            cart_str = myjson.dumps(cart_dict)
            response.set_cookie('cart', cart_str, max_age=constants.CART_COOKIE_EXPIRES)
        else:
            # 用户已登录,则存入redis   {sku_id:{'count':xxx, 'selected':True},...}
            # 连接redis
            redis_conn = get_redis_connection('cart')
            # 构造键,因为服务器会存多个用户的购物车信息,用过用户编号可以区分
            key = 'cart_%d' % request.user.id
            key_select = 'cart_selected_%d' % request.user.id
            # 将商品编号/数量存入hash中
            redis_conn.hset(key, sku_id, count)
            # 将商品编号存入set中,表示选中此商品
            redis_conn.sadd(key_select, sku_id)

        return response

    def get(self, request):
        """
        查询购物车数据
        请求方式: GET /cart/
        请求参数: 无
        返回数据: id(商品编号int)  count(数量int)  selected(是否勾选,默认勾选bool)  name(商品名称str)  default_image_url(商品默认图片str)  price(商品单价decimal)
        """
        try:
            user = request.user
        except Exception:
            user = None

        if user is None:
            # 未登录,读取cookie
            # 获取cookie中购物车信息
            cart_str = request.COOKIES.get('cart')
            if cart_str is not None:
                cart_dict = myjson.loads(cart_str)
            else:
                cart_dict = {}
            # 根据商品编号查询对象,并添加数量/选中属性
            skus = []
            for key, value in cart_dict.items():
                sku = SKU.objects.get(pk=key)
                sku.count = value['count']
                sku.selected = value['selected']
                skus.append(sku)
        else:
            # 已登录
            # 连接redis
            redis_conn = get_redis_connection('cart')
            # 构造键,因为服务器会存入多个用户的购物车信息,通过用户编号可以区分
            key = 'cart_%d' % request.user.id
            key_select = 'cart_selected_%d' % request.user.id
            # 从hash中读取所有商品编号
            sku_ids = redis_conn.hkeys(key)  # [1,2,3]
            # 读取选中的商品编号
            sku_ids_selected = redis_conn.smembers(key_select)
            # 从redis中取出来的是bytes类型
            sku_ids_selected = [int(sku_id) for sku_id in sku_ids_selected]
            # 查询商品
            skus = SKU.objects.filter(pk__in=sku_ids)
            # 便利商品,增加数量/选中属性
            for sku in skus:
                sku.count = redis_conn.hget(key, sku.id)
                sku.selected = sku.id in sku_ids_selected
        # 序列化输出
        serializer = CartSKUSerializer(skus, many=True)
        return Response(serializer.data)

    def put(self, request):
        """
        修改购物车数据
        请求方式: PUT /cart/
        请求参数: sku_id(商品编号int)  count(数量int)  selected(是否勾选,默认勾选bool)
        返回数据: sku_id(商品编号int)  count(数量int)  selected(是否勾选,默认勾选bool)
        """
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        sku_id = serializer.validated_data.get('sku_id')
        count = serializer.validated_data.get('count')
        selected = serializer.validated_data.get('selected')

        response = Response(serializer.validated_data)

        try:
            user = request.user
        except Exception:
            user = None

        if user is None:
            """未登录则保存至cookie"""
            cart_str = request.COOKIES.get('cart')
            if cart_str is not None:
                cart_dict = myjson.loads(cart_str)
            else:
                cart_dict = {}
            # 修改
            cart_dict[sku_id] = {
                'count': count,  # 这里以当前数据为准,直接覆盖原cookie中数据
                'selected': selected
            }
            # 保存
            cart_str = myjson.dumps(cart_dict)
            response.set_cookie('cart', cart_str, max_age=constants.CART_COOKIE_EXPIRES)
        else:
            # 已登录则存入redis
            redis_conn = get_redis_connection('cart')
            key = 'cart_%d' % request.user.id
            key_select = 'cart_selected_%d' % request.user.id
            # 修改数量
            redis_conn.hset(key, sku_id, count)
            # 修改选中
            if selected:
                redis_conn.sadd(key_select, sku_id)
            else:
                redis_conn.srem(key_select, sku_id)

        return response

    def delete(self, request):
        """
        删除购物车数据
        请求方式: DELETE /cart/
        请求参数: sku_id(商品编号int)
        返回数据: 无/状态码204
        """
        serializer = CartDeleteSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        sku_id = serializer.validated_data['sku_id']

        try:
            user = request.user
        except Exception:
            user = None

        response = Response(status=204)

        if user is None:
            cart_str = request.COOKIES.get('cart')
            if cart_str is not None:
                cart_dict = myjson.loads(cart_str)
            else:
                cart_dict = {}
            # 删除cookie中数据
            if sku_id in cart_str:
                del cart_dict[sku_id]
            # 删除后再存入cookie
                cart_str = myjson.dumps(cart_dict)
                response.set_cookie('cart', cart_str, max_age=constants.CART_COOKIE_EXPIRES)
        else:
            redis_conn = get_redis_connection('cart')
            key = 'cart_%d' % request.user.id
            key_select = 'cart_selected_%d' % request.user.id
            redis_conn.hdel(key, sku_id)
            redis_conn.srem(key_select, sku_id)

        return response


class CartSelectAllView(APIView):
    """
    购物车全选
    请求方式: PUT /cart/selection/
    请求参数: selected(是否全选,true表示全选,false表示取消全选bool)
    返回数据: message(OK str)
    """
    def perform_authentication(self, request):
        """重写父类的用户验证方法,不在进入视图前就检查JWT"""
        pass

    def put(self, request):
        serializer = CartSelectAllSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        selected = serializer.validated_data['selected']

        response = Response({'message': 'ok'})

        try:
            user = request.user
        except Exception:
            user = None

        if user is None:
            cart_str = request.COOKIES.get('cart')
            if cart_str is not None:
                cart_dict = myjson.loads(cart_str)
            else:
                cart_dict = {}
            for key in cart_dict.keys():
                cart_dict[key]['selected'] = selected
            cart_str = myjson.dumps((cart_dict))
            response.set_cookie('cart', cart_str, max_age=constants.CART_COOKIE_EXPIRES)
        else:
            redis_conn = get_redis_connection('cart')
            key = 'cart_%d' % request.user.id
            key_select = 'cart_selected_%d' % request.user.id
            sku_ids = redis_conn.hkeys(key)
            if selected:
                redis_conn.sadd(key_select, *sku_ids)
            else:
                redis_conn.srem(key_select, *sku_ids)
        return response
